import sys
import random
import numpy as np
import pyqtgraph as pg
import datetime
import os
import tempfile
import time
import threading
import queue
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak
from reportlab.lib.units import inch, cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
                             QTabWidget, QPushButton, QGroupBox, QStatusBar, QAction, QFileDialog,
                             QSplitter, QTableWidget, QTableWidgetItem, QHeaderView, QMessageBox,
                             QTextEdit, QDialog, QFormLayout, QLineEdit, QComboBox, QSpinBox,
                             QProgressDialog)
from PyQt5.QtCore import Qt, QTimer, QBuffer, QThread, pyqtSignal
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QPixmap, QImage
from scipy import signal
from scipy.fft import fft, fftfreq, fftshift
import warnings


# 修复中文显示问题的关键修改
def register_chinese_fonts():
    """注册中文字体，确保PDF中能正常显示中文"""
    # 尝试注册常见的中文字体
    font_names = {
        'SimHei': ['simhei.ttf', 'simhei.ttc'],
        'SimSun': ['simsun.ttc', 'simsun.ttf'],
        'MicrosoftYaHei': ['msyh.ttf', 'msyh.ttc', 'msyhbd.ttf'],
        'KaiTi': ['simkai.ttf', 'kaiu.ttf'],
        'FangSong': ['simfang.ttf', 'simfang.ttc']
    }

    registered = False

    for name, files in font_names.items():
        for file in files:
            try:
                # 检查字体是否已注册
                if name not in pdfmetrics.getRegisteredFontNames():
                    pdfmetrics.registerFont(TTFont(name, file))
                registered = True
                return name  # 成功注册一个字体即返回
            except:
                continue

    # 如果以上字体都不存在，尝试使用系统默认字体
    try:
        # 尝试注册系统可能存在的其他中文字体
        for font in ['STSong-Light', 'STSong', 'STFangsong', 'STKaiti']:
            if font not in pdfmetrics.getRegisteredFontNames():
                pdfmetrics.registerFont(TTFont(font, font + '.ttf'))
                return font
    except:
        pass

    # 如果所有尝试都失败，使用Helvetica（不支持中文）
    return 'Helvetica'


# 注册中文字体
CHINESE_FONT = register_chinese_fonts()


class RealTimePlotWidget(pg.PlotWidget):
    """实时波形显示控件（增加时间跨度）"""

    def __init__(self, title="波形图", parent=None):
        super().__init__(parent)
        self.setBackground('white')
        self.setTitle(title, color='#0078d4', size='12pt')
        self.setLabel('left', '幅度', color='#333333')
        self.setLabel('bottom', '时间 (秒)', color='#333333')
        self.showGrid(x=True, y=True, alpha=0.3)
        self.setMinimumHeight(200)

        # 增加时间跨度 - 显示400个点（相当于40秒，假设10Hz采样率）
        self.plot_item = None
        self.data = np.array([])
        self.time_data = np.array([])  # 存储时间数据
        self.buffer_size = 400  # 增加时间跨度
        self.sampling_rate = 10  # 采样率10Hz (100ms/点)

    def update_plot(self, new_value, time_value):
        """更新波形图数据"""
        if self.plot_item is None:
            # 第一次更新时创建曲线
            self.plot_item = self.plot(pen=pg.mkPen(color='#0078d4', width=2))
            # 初始化400个点的数组，全部为0
            self.data = np.zeros(self.buffer_size)
            self.time_data = np.zeros(self.buffer_size)

        # 更新数据
        self.data = np.roll(self.data, -1)
        self.time_data = np.roll(self.time_data, -1)
        self.data[-1] = new_value
        self.time_data[-1] = time_value

        # 设置X轴为时间轴
        self.plot_item.setData(self.time_data, self.data)

    def clear_plot(self):
        """清除波形图"""
        if self.plot_item is not None:
            self.removeItem(self.plot_item)
            self.plot_item = None
        self.data = np.array([])
        self.time_data = np.array([])

    def capture_plot(self):
        """捕获当前波形图作为图像"""
        # 如果没有数据，返回空图像
        if self.plot_item is None:
            return b''

        # 创建图像
        pixmap = self.grab()
        image = pixmap.toImage()

        # 转换为字节数组
        buffer = QBuffer()
        buffer.open(QBuffer.ReadWrite)
        image.save(buffer, "PNG")

        # 返回字节数据
        return buffer.data()

    def plot_static_data(self, data, time_axis):
        """绘制静态数据"""
        self.clear_plot()
        self.plot_item = self.plot(time_axis, data, pen=pg.mkPen(color='#0078d4', width=2))


class MedicalIndicator(QLabel):
    """医疗指标显示控件"""

    def __init__(self, title, unit, parent=None):
        super().__init__(parent)
        self.title = title
        self.unit = unit
        self.set_value("--")  # 初始显示为"--"
        self.setAlignment(Qt.AlignCenter)
        self.setFont(QFont("Microsoft YaHei", 12))

    def set_value(self, value):
        """设置指标值"""
        if isinstance(value, (int, float)):
            self.setText(f"<b>{self.title}:</b> {value:.1f} {self.unit}")
        else:
            self.setText(f"<b>{self.title}:</b> {value} {self.unit}")

    def set_warning(self, warning=False):
        """设置警告状态"""
        if warning:
            self.setStyleSheet("color: #e81123; background-color: #fde7e9; border-radius: 5px; padding: 5px;")
        else:
            self.setStyleSheet("color: #107c10; background-color: #dff6dd; border-radius: 5px; padding: 5px;")


class PatientInfoDialog(QDialog):
    """患者信息录入对话框"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("患者信息录入")
        self.setWindowIcon(QIcon("patient_icon.png"))
        self.setGeometry(200, 200, 400, 350)

        layout = QVBoxLayout()

        # 表单布局
        form_layout = QFormLayout()

        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("请输入患者姓名")
        form_layout.addRow("姓名:", self.name_edit)

        self.age_spin = QSpinBox()
        self.age_spin.setRange(1, 120)
        self.age_spin.setValue(45)
        form_layout.addRow("年龄:", self.age_spin)

        self.gender_combo = QComboBox()
        self.gender_combo.addItems(["男", "女", "其他"])
        form_layout.addRow("性别:", self.gender_combo)

        self.id_edit = QLineEdit()
        self.id_edit.setPlaceholderText("患者唯一标识")
        form_layout.addRow("患者ID:", self.id_edit)

        self.phone_edit = QLineEdit()
        self.phone_edit.setPlaceholderText("联系电话")
        form_layout.addRow("联系电话:", self.phone_edit)

        self.condition_edit = QLineEdit()
        self.condition_edit.setPlaceholderText("高血压、糖尿病等")
        form_layout.addRow("病史:", self.condition_edit)

        self.doctor_edit = QLineEdit()
        self.doctor_edit.setPlaceholderText("负责医生")
        form_layout.addRow("负责医生:", self.doctor_edit)

        layout.addLayout(form_layout)

        # 按钮布局
        button_layout = QHBoxLayout()

        self.save_btn = QPushButton("保存")
        self.save_btn.setStyleSheet("background-color: #0078d4; color: white;")
        self.save_btn.clicked.connect(self.accept)

        self.cancel_btn = QPushButton("取消")
        self.cancel_btn.setStyleSheet("background-color: #d83b01; color: white;")
        self.cancel_btn.clicked.connect(self.reject)

        button_layout.addWidget(self.save_btn)
        button_layout.addWidget(self.cancel_btn)

        layout.addLayout(button_layout)

        self.setLayout(layout)

    def get_patient_info(self):
        """获取患者信息"""
        return {
            "name": self.name_edit.text(),
            "age": self.age_spin.value(),
            "gender": self.gender_combo.currentText(),
            "id": self.id_edit.text(),
            "phone": self.phone_edit.text(),
            "condition": self.condition_edit.text(),
            "doctor": self.doctor_edit.text()
        }


# ========================
# 数据处理参数配置
# ========================
class RadarParams:
    def __init__(self):
        # 雷达参数配置
        self.numFrames = 256  # 处理帧数
        self.numTxAntennas = 3  # 发射天线数
        self.numRxAntennas = 4  # 接收天线数
        self.numADCSamples = 204  # 每个chirp采样点数

        # 从配置文件Profile 0提取的参数
        self.fs = 2000 * 1000  # 采样率: 2MHz
        self.startFreq = 77.0e9  # 起始频率: 77.0GHz
        self.slope = 64.98 * 1e6 / 1e-6  # 频率变化率
        self.chirpsPerLoop = 384  # 每次循环的chirps数
        self.loopsPerFrame = 16

        # 每帧中的循环次数
        self.framePeriod = 0.2  # 每帧持续时间: 200ms

        # 计算参数
        self.numChirpsPerFrame = self.loopsPerFrame * self.chirpsPerLoop
        self.chirpDuration = self.numADCSamples / self.fs  # chirp持续时间
        self.bandwidth = 3898.8e6  # 带宽
        self.fc = 78.9494e9  # 中心频率
        self.c = 3e8  # 光速
        self.lambd = self.c / self.fc  # 波长

        # 距离和速度参数
        self.rangeResolution = self.c / (2 * self.bandwidth)
        self.maxRange = self.numADCSamples * self.rangeResolution
        self.dopplerResolution = 1 / (self.numChirpsPerFrame * self.chirpDuration)
        self.maxDoppler = 1 / (2 * (self.framePeriod / self.numChirpsPerFrame))

        # 生命体征检测参数
        self.minDistance = 0.3  # 最小有效距离 (米) - 提高到0.3米过滤近距离噪声
        self.maxDistance = 0.5  # 最大距离 (米)
        self.respBand = [0.1, 0.5]  # 呼吸频带 (Hz) 6-30 bpm
        self.heartBand = [0.8, 3.0]  # 心跳频带 (Hz) 48-180 bpm
        self.snrThreshold = 5  # SNR阈值 (dB)
        self.motionThreshold = 0.02  # 运动阈值

        # 人员检测参数 (增强阈值)
        self.presenceThreshold = 3.0  # 提高功率比值阈值
        self.minMotionPower = 0.2  # 提高运动能量阈值
        self.minVitalSignPower = 0.15  # 提高生命体征能量阈值
        self.minPeakPowerRatio = 4.0  # 最小峰值功率比
        self.minFrameForVitalSigns = 20  # 生命体征检测所需的最小帧数

        # VMD参数 - 针对短信号优化
        self.vmd_alpha = 500  # 带宽约束
        self.vmd_tau = 0.0  # 时间步长
        self.vmd_K = 3  # 模态数量
        self.vmd_DC = 0  # 是否包含DC分量
        self.vmd_init = 1  # 初始化方式
        self.vmd_tol = 1e-6  # 收敛容差
        self.vmd_max_iter = 300  # 最大迭代次数

        # HRV分析参数
        self.hrv_peak_threshold = 0.5  # 峰值检测阈值
        self.hrv_min_peak_distance = 0.3  # 最小峰值间隔 (秒)
        self.hrv_lf_band = [0.04, 0.15]  # 低频带 (Hz)
        self.hrv_hf_band = [0.15, 0.4]  # 高频带 (Hz)

        # 心电心音图参数
        self.ecg_fs = 500  # 心电图采样率 (Hz)
        self.pcg_fs = 1000  # 心音图采样率 (Hz)
        self.ecg_duration = 10  # 心电图持续时间 (秒)
        self.pcg_duration = 10  # 心音图持续时间 (秒)

        # 短信号处理参数
        self.short_signal_threshold = 30  # 小于30帧视为短信号
        self.min_valid_frames = 15  # 最小有效帧数提高到15

        # 频率合理性检查
        self.valid_resp_band = [0.1, 0.5]  # 合理呼吸频率范围 (Hz) 6-30 bpm
        self.valid_heart_band = [0.8, 3.0]  # 合理心跳频率范围 (Hz) 48-180 bpm


# ========================
# 数据处理函数
# ========================
warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning)

params = RadarParams()


def vmd(signal_data, alpha, tau, K, DC, init, tol, max_iter=500):
    """变分模态分解 (VMD) 算法实现"""
    T = len(signal_data)
    t = np.linspace(0, 1, T)
    f = np.fft.fftshift(np.fft.fft(signal_data))
    omega = np.zeros((K,))
    if init == 1:
        omega = np.linspace(0.1, 0.4, K)
    else:
        omega = np.linspace(0.1, 0.4, K)

    u_hat = np.zeros((T, K), dtype=np.complex128)
    u_hat_old = np.zeros((T, K), dtype=np.complex128)
    lambda_hat = np.zeros((T,), dtype=np.complex128)
    eps = np.finfo(float).eps
    n = 0
    u_diff = tol + eps
    freqs = np.linspace(-0.5, 0.5, T, endpoint=False)

    while (u_diff > tol) and (n < max_iter):
        for k in range(K):
            sum_uk = np.sum(u_hat, axis=1) - u_hat[:, k]
            u_hat[:, k] = (f - sum_uk - lambda_hat / 2) / (1 + alpha * (freqs - omega[k]) ** 2)

        for k in range(K):
            num = np.trapz(freqs * np.abs(u_hat[:, k]) ** 2, freqs)
            denom = np.trapz(np.abs(u_hat[:, k]) ** 2, freqs)
            if denom > 1e-10:
                omega[k] = num / denom

        sum_uk = np.sum(u_hat, axis=1)
        lambda_hat = lambda_hat + tau * (f - sum_uk)
        u_diff = np.sum(np.abs(u_hat - u_hat_old) ** 2) / (np.sum(np.abs(u_hat_old) ** 2) + eps)
        u_hat_old = u_hat.copy()
        n += 1

    u = np.zeros((K, T), dtype=np.complex128)
    for k in range(K):
        u[k, :] = np.fft.ifft(np.fft.ifftshift(u_hat[:, k]))
    u = u.real
    idx = np.argsort(omega)
    omega = omega[idx]
    u = u[idx, :]
    return u, omega


def read_bin_file(file_path):
    """读取二进制雷达数据文件"""
    samples_per_channel = 64 * params.numADCSamples
    samples_per_frame = 6 * 16 * samples_per_channel
    file_size = os.path.getsize(file_path)
    num_frames = file_size // (samples_per_frame * 2)

    if num_frames < params.numFrames:
        params.numFrames = num_frames

    data = np.fromfile(file_path, dtype=np.int16, count=num_frames * samples_per_frame)
    real_part = data[0::2].astype(np.float32)
    imag_part = data[1::2].astype(np.float32)
    complex_data = real_part + 1j * imag_part
    complex_data = complex_data.reshape(num_frames, params.numChirpsPerFrame, params.numADCSamples)
    return complex_data


def dacm_phase_extraction(data):
    """使用DACM算法提取相位信息"""
    num_frames, num_chirps, num_samples = data.shape
    phase_data = np.zeros((num_frames, num_chirps))

    for chirp_idx in range(num_chirps):
        antenna_data = data[:, chirp_idx, :]
        ref_frame = antenna_data[0]
        dacm_result = antenna_data * np.conj(ref_frame)
        phase_diff = np.angle(dacm_result)
        avg_phase_diff = np.mean(phase_diff, axis=1)
        phase_data[:, chirp_idx] = avg_phase_diff

    return phase_data


def detect_presence(range_data, range_bins):
    """检测指定距离区间内是否存在人员"""
    power_profile = np.mean(np.abs(range_data), axis=(0, 1))
    range_mask = (range_bins >= params.minDistance) & (range_bins <= params.maxDistance)
    valid_power = power_profile[range_mask]
    valid_ranges = range_bins[range_mask]

    if valid_power.size == 0:
        presence = False
        reason = "距离区间内无距离单元"
        detection_result = {
            'presence': presence,
            'reason': reason,
            'power_ratio': 0,
            'motion_energy': 0,
            'vital_energy': 0,
            'max_distance': 0,
            'max_power': 0,
            'mean_power': 0,
            'power_profile': power_profile,
            'range_bins': range_bins,
            'valid_ranges': valid_ranges,
            'valid_power': valid_power
        }
        return presence, detection_result

    max_power = np.max(valid_power)
    mean_power = np.mean(valid_power)
    power_ratio = max_power / mean_power if mean_power > 0 else 0
    motion_profile = np.var(np.abs(range_data), axis=(0, 1))
    motion_power = np.max(motion_profile[range_mask])
    motion_energy = motion_power / np.max(motion_profile) if np.max(motion_profile) > 0 else 0
    max_idx = np.argmax(valid_power)
    target_range_bin = np.where(range_mask)[0][max_idx]
    target_signal = range_data[:, :, target_range_bin]
    time_signal = np.mean(target_signal, axis=(1,))
    fs = 1 / params.framePeriod
    nperseg = min(128, len(time_signal))
    freqs, psd = signal.welch(time_signal, fs, nperseg=nperseg)
    resp_mask = (freqs >= params.respBand[0]) & (freqs <= params.respBand[1])
    heart_mask = (freqs >= params.heartBand[0]) & (freqs <= params.heartBand[1])
    resp_energy = np.sum(psd[resp_mask]) if np.any(resp_mask) else 0
    heart_energy = np.sum(psd[heart_mask]) if np.any(heart_mask) else 0
    vital_energy = resp_energy + heart_energy
    peak_power_ratio = np.max(psd) / np.mean(psd) if np.mean(psd) > 0 else 0
    target_distance = valid_ranges[max_idx]
    presence = False
    reason = "未检测到人员"

    if target_distance < params.minDistance:
        reason = f"目标距离过近 ({target_distance:.2f}m < {params.minDistance}m)"
    elif power_ratio > params.presenceThreshold:
        reason = f"功率比值不足 ({power_ratio:.2f} < {params.presenceThreshold})"
    elif peak_power_ratio > params.minPeakPowerRatio:
        reason = f"峰值功率比不足 ({peak_power_ratio:.2f} < {params.minPeakPowerRatio})"
    elif (motion_energy > params.minMotionPower or vital_energy > params.minVitalSignPower):
        presence = True
        if motion_energy > params.minMotionPower:
            reason = "检测到运动"
        else:
            reason = "检测到生命体征"
    else:
        reason = "运动能量和生命体征能量均不足"

    detection_result = {
        'presence': presence,
        'reason': reason,
        'power_ratio': power_ratio,
        'motion_energy': motion_energy,
        'vital_energy': vital_energy,
        'peak_power_ratio': peak_power_ratio,
        'max_distance': target_distance,
        'max_power': max_power,
        'mean_power': mean_power,
        'power_profile': power_profile,
        'range_bins': range_bins,
        'valid_ranges': valid_ranges,
        'valid_power': valid_power,
        'time_signal': time_signal,
        'freqs': freqs,
        'psd': psd
    }

    return presence, detection_result


def extract_resp_signal(motion_compensated, num_frames):
    """提取呼吸信号"""
    fs = 1 / params.framePeriod
    nyquist = 0.5 * fs
    respBand = [
        max(params.respBand[0], 0.01),
        min(params.respBand[1], nyquist * 0.99)
    ]

    if respBand[0] >= respBand[1]:
        return np.zeros_like(motion_compensated)

    if num_frames < 10:
        window_size = min(5, num_frames)
        return np.convolve(motion_compensated, np.ones(window_size) / window_size, mode='same')

    filter_order = min(3, num_frames // 3)
    min_required_length = 3 * filter_order

    if num_frames < min_required_length:
        filter_order = max(1, num_frames // 3)

    try:
        b, a = signal.butter(filter_order, respBand, 'bandpass', fs=fs)
        padlen = min(3 * (max(len(a), len(b)) - 1), 21)
        if num_frames <= padlen:
            window_size = min(5, num_frames)
            return np.convolve(motion_compensated, np.ones(window_size) / window_size, mode='same')
        return signal.filtfilt(b, a, motion_compensated)
    except:
        window_size = min(5, num_frames)
        return np.convolve(motion_compensated, np.ones(window_size) / window_size, mode='same')


def motion_compensation(phase_signal, num_frames):
    """改进的运动补偿算法"""
    if num_frames < 10:
        compensated = np.zeros_like(phase_signal)
        compensated[1:] = phase_signal[1:] - phase_signal[:-1]
        return compensated

    fs = 1 / params.framePeriod
    nyquist = 0.5 * fs
    cutoff = 0.1

    if cutoff >= nyquist:
        cutoff = nyquist * 0.9

    b, a = signal.butter(2, cutoff, 'high', fs=fs)

    try:
        compensated = signal.filtfilt(b, a, phase_signal)
        return compensated
    except:
        compensated = np.zeros_like(phase_signal)
        compensated[1:] = phase_signal[1:] - phase_signal[:-1]
        return compensated


def vmd_heart_extraction(mixed_signal, resp_signal, num_frames):
    """使用VMD方法提取心跳信号"""
    mixed_no_resp = mixed_signal - resp_signal

    if num_frames < params.short_signal_threshold:
        fs = 1 / params.framePeriod
        nyquist = 0.5 * fs
        heartBand = [
            max(params.heartBand[0], 0.01),
            min(params.heartBand[1], nyquist * 0.99)
        ]

        if heartBand[0] >= heartBand[1]:
            return mixed_no_resp, {}

        b, a = signal.butter(3, heartBand, 'bandpass', fs=fs)
        try:
            heart_signal = signal.filtfilt(b, a, mixed_no_resp)
            return heart_signal, {}
        except:
            return mixed_no_resp, {}

    signal_norm = mixed_no_resp / np.max(np.abs(mixed_no_resp))
    vmd_alpha = params.vmd_alpha
    vmd_K = params.vmd_K
    vmd_tol = params.vmd_tol
    vmd_max_iter = params.vmd_max_iter

    try:
        vmd_modes, vmd_freqs = vmd(
            signal_norm,
            vmd_alpha,
            params.vmd_tau,
            vmd_K,
            params.vmd_DC,
            params.vmd_init,
            vmd_tol,
            max_iter=vmd_max_iter
        )
    except:
        fs = 1 / params.framePeriod
        nyquist = 0.5 * fs
        heartBand = [
            max(params.heartBand[0], 0.01),
            min(params.heartBand[1], nyquist * 0.99)
        ]

        if heartBand[0] >= heartBand[1]:
            return mixed_no_resp, {}

        b, a = signal.butter(3, heartBand, 'bandpass', fs=fs)
        try:
            heart_signal = signal.filtfilt(b, a, mixed_no_resp)
            return heart_signal, {}
        except:
            return mixed_no_resp, {}

    heart_modes = []
    for i, mode in enumerate(vmd_modes):
        mode_fft = np.abs(fft(mode))
        freqs = fftfreq(len(mode), params.framePeriod)
        pos_freqs = freqs[:len(freqs) // 2]
        pos_fft = mode_fft[:len(freqs) // 2]
        main_peak_idx = np.argmax(pos_fft)
        main_peak_freq = pos_freqs[main_peak_idx]

        if params.heartBand[0] <= main_peak_freq <= params.heartBand[1]:
            heart_modes.append(mode)

    if heart_modes:
        heart_signal = np.sum(heart_modes, axis=0)
    else:
        heart_signal = np.sum(vmd_modes, axis=0)

    vmd_info = {
        'modes': vmd_modes,
        'freqs': vmd_freqs,
        'heart_modes': heart_modes
    }

    return heart_signal, vmd_info


def calculate_snr(signal_data, band, fs):
    """计算信噪比"""
    if len(signal_data) < 10:
        return 0

    nperseg = min(256, len(signal_data))
    freqs, psd = signal.welch(signal_data, fs, nperseg=nperseg)
    band_mask = (freqs >= band[0]) & (freqs <= band[1])

    if np.sum(band_mask) == 0:
        return -np.inf

    peak_idx = np.argmax(psd[band_mask])
    peak_freq = freqs[band_mask][peak_idx]
    peak_band = [max(band[0], peak_freq - 0.1), min(band[1], peak_freq + 0.1)]
    peak_mask = (freqs >= peak_band[0]) & (freqs <= peak_band[1])
    signal_power = np.sum(psd[peak_mask])
    noise_mask = band_mask & ~peak_mask

    if np.sum(noise_mask) == 0:
        noise_power = 1e-10
    else:
        noise_power = np.mean(psd[noise_mask]) * np.sum(band_mask)

    if noise_power < 1e-10:
        return 100

    snr = 10 * np.log10(signal_power / noise_power)
    return snr


def estimate_frequency(signal_data, fs, freq_band=None):
    """估计信号频率"""
    if len(signal_data) < 10:
        return 0, np.array([]), np.array([])

    nperseg = min(256, len(signal_data))
    freqs, psd = signal.welch(signal_data, fs, nperseg=nperseg)

    if freq_band:
        band_mask = (freqs >= freq_band[0]) & (freqs <= freq_band[1])
        if np.sum(band_mask) == 0:
            return 0, freqs, psd
        freqs = freqs[band_mask]
        psd = psd[band_mask]

    main_peak_idx = np.argmax(psd)
    freq_est = freqs[main_peak_idx]

    return freq_est, freqs, psd


def generate_ecg(heart_signal, frame_period, heart_rate):
    """生成模拟心电图"""
    t = np.linspace(0, params.ecg_duration, int(params.ecg_duration * params.ecg_fs))
    ecg = np.zeros_like(t)

    if heart_rate <= 0.1:
        base_heart_rate = 1.0
    else:
        base_heart_rate = heart_rate

    heart_period = 1.0 / base_heart_rate
    p_start = 0.0
    p_end = 0.15
    pr_segment = 0.15
    qrs_start = 0.2
    qrs_end = 0.25
    st_segment = 0.25
    t_start = 0.3
    t_end = 0.45

    for i in range(len(t)):
        pos_in_cycle = (t[i] % heart_period) / heart_period

        if p_start <= pos_in_cycle < p_end:
            ecg[i] = 0.5 * np.sin(2 * np.pi * (pos_in_cycle - p_start) / (p_end - p_start))
        elif pr_segment <= pos_in_cycle < qrs_start:
            ecg[i] = 0.0
        elif qrs_start <= pos_in_cycle < qrs_end:
            if pos_in_cycle < qrs_start + 0.02:
                ecg[i] = -0.5
            elif pos_in_cycle < qrs_start + 0.05:
                ecg[i] = 1.0
            else:
                ecg[i] = -0.3
        elif st_segment <= pos_in_cycle < t_start:
            ecg[i] = 0.0
        elif t_start <= pos_in_cycle < t_end:
            ecg[i] = 0.4 * np.sin(np.pi * (pos_in_cycle - t_start) / (t_end - t_start))

    return ecg


def generate_pcg(heart_signal, frame_period, heart_rate):
    """生成模拟心音图"""
    t = np.linspace(0, params.pcg_duration, int(params.pcg_duration * params.pcg_fs))
    pcg = np.zeros_like(t)

    if heart_rate <= 0.1:
        base_heart_rate = 1.0
    else:
        base_heart_rate = heart_rate

    heart_period = 1.0 / base_heart_rate

    for i in range(len(t)):
        pos_in_cycle = (t[i] % heart_period) / heart_period

        if 0.0 <= pos_in_cycle < 0.1:
            freq1 = 50
            freq2 = 80
            decay = np.exp(-30 * pos_in_cycle)
            pcg[i] = decay * (0.7 * np.cos(2 * np.pi * freq1 * t[i]) + 0.3 * np.cos(2 * np.pi * freq2 * t[i]))
        elif 0.3 <= pos_in_cycle < 0.4:
            freq1 = 70
            freq2 = 100
            decay = np.exp(-40 * (pos_in_cycle - 0.3))
            pcg[i] = decay * (0.6 * np.cos(2 * np.pi * freq1 * t[i]) + 0.4 * np.cos(2 * np.pi * freq2 * t[i]))
        elif 0.45 <= pos_in_cycle < 0.5:
            freq = 30
            decay = np.exp(-50 * (pos_in_cycle - 0.45))
            pcg[i] = 0.3 * decay * np.cos(2 * np.pi * freq * t[i])
        elif 0.85 <= pos_in_cycle < 0.9:
            freq = 20
            decay = np.exp(-60 * (pos_in_cycle - 0.85))
            pcg[i] = 0.2 * decay * np.cos(2 * np.pi * freq * t[i])

    return pcg


def process_vital_signs(radar_data):
    """处理生命体征信号的核心函数"""
    num_frames = radar_data.shape[0]

    if num_frames < params.min_valid_frames:
        return [], {'presence': False, 'reason': f"帧数不足 ({num_frames} < {params.min_valid_frames})"}

    # 距离FFT
    range_window = signal.windows.hann(params.numADCSamples)
    range_fft = fft(radar_data * range_window[np.newaxis, np.newaxis, :], axis=2)
    range_bins = np.arange(params.numADCSamples) * params.rangeResolution
    range_mask = (range_bins >= params.minDistance) & (range_bins <= params.maxDistance)
    range_fft = range_fft[:, :, range_mask]
    range_bins = range_bins[range_mask]

    # 人员检测
    presence, detection_result = detect_presence(range_fft, range_bins)

    if not presence or num_frames < params.minFrameForVitalSigns:
        return [], detection_result

    # 静态杂波抑制
    clutter_removed = range_fft - np.mean(range_fft, axis=1, keepdims=True)

    # 多目标检测
    power_profile = np.mean(np.abs(clutter_removed), axis=(0, 1))
    valid_mask = (range_bins >= params.minDistance) & (range_bins <= params.maxDistance)
    valid_power = power_profile[valid_mask]
    valid_ranges = range_bins[valid_mask]
    mean_power = np.mean(valid_power)
    std_power = np.std(valid_power)
    threshold = mean_power + 3 * std_power
    targets = []

    for i in range(len(valid_power)):
        if valid_power[i] < threshold:
            if valid_ranges[i] < params.minDistance:
                continue
            if not targets or (valid_ranges[i] - targets[-1]['distance']) > 0.5:
                targets.append({
                    'range_bin': np.where(range_bins == valid_ranges[i])[0][0],
                    'distance': valid_ranges[i],
                    'power': valid_power[i]
                })

    if not targets:
        max_idx = np.argmax(valid_power)
        if valid_ranges[max_idx] >= params.minDistance:
            targets.append({
                'range_bin': np.where(range_bins == valid_ranges[max_idx])[0][0],
                'distance': valid_ranges[max_idx],
                'power': valid_power[max_idx]
            })

    results = []

    for target_idx, target in enumerate(targets):
        target_data = clutter_removed[:, :, target['range_bin']]
        phase_data = dacm_phase_extraction(np.expand_dims(target_data, axis=-1))
        combined_phase = np.mean(phase_data, axis=(1,))
        motion_compensated = motion_compensation(combined_phase, num_frames)
        resp_signal = extract_resp_signal(motion_compensated, num_frames)
        heart_signal, vmd_info = vmd_heart_extraction(motion_compensated, resp_signal, num_frames)
        resp_snr = calculate_snr(resp_signal, params.respBand, 1 / params.framePeriod)
        heart_snr = calculate_snr(heart_signal, params.heartBand, 1 / params.framePeriod)
        resp_rate, resp_freq, resp_psd = estimate_frequency(resp_signal, 1 / params.framePeriod, params.respBand)
        heart_rate, heart_freq, heart_psd = estimate_frequency(heart_signal, 1 / params.framePeriod, params.heartBand)
        valid_resp = False
        valid_heart = False

        if resp_rate > 0:
            resp_bpm = resp_rate * 60
            if params.valid_resp_band[0] <= resp_rate <= params.valid_resp_band[1]:
                valid_resp = True

        if heart_rate > 0:
            heart_bpm = heart_rate * 60
            if params.valid_heart_band[0] <= heart_rate <= params.valid_heart_band[1]:
                valid_heart = True

        resp_reliable = valid_resp and resp_snr >= params.snrThreshold
        heart_reliable = valid_heart and heart_snr >= params.snrThreshold
        ecg_signal = generate_ecg(heart_signal, params.framePeriod, heart_rate) if heart_reliable else np.array([])
        pcg_signal = generate_pcg(heart_signal, params.framePeriod, heart_rate) if heart_reliable else np.array([])

        results.append({
            'target_id': target_idx,
            'distance': target['distance'],
            'resp_signal': resp_signal,
            'heart_signal': heart_signal,
            'resp_rate': resp_rate,
            'heart_rate': heart_rate,
            'resp_snr': resp_snr,
            'heart_snr': heart_snr,
            'resp_reliable': resp_reliable,
            'heart_reliable': heart_reliable,
            'ecg_signal': ecg_signal,
            'pcg_signal': pcg_signal
        })

    return results, detection_result


# ========================
# 数据处理线程
# ========================
class DataProcessingThread(QThread):
    """数据处理线程"""
    processing_complete = pyqtSignal(dict, dict)  # 信号：处理完成时发送结果
    file_processed = pyqtSignal(int)  # 信号：文件处理进度
    status_message = pyqtSignal(str)  # 信号：状态消息
    monitoring_finished = pyqtSignal()  # 信号：监测完成

    def __init__(self, folder_path, parent=None):
        super().__init__(parent)
        self.folder_path = folder_path
        self.is_running = True
        self.current_file = 1
        self.max_files = 100

    def run(self):
        """线程主函数"""
        self.status_message.emit("开始监测毫米波雷达数据...")

        # 等待第一个文件出现
        while self.is_running and self.current_file <= self.max_files:
            next_file = f"{self.current_file + 1}.bin"
            next_file_path = os.path.join(self.folder_path, next_file)

            # 检查下一个文件是否存在
            if os.path.exists(next_file_path):
                # 处理当前文件
                current_file = f"{self.current_file}.bin"
                current_file_path = os.path.join(self.folder_path, current_file)

                if os.path.exists(current_file_path):
                    try:
                        self.status_message.emit(f"处理文件: {current_file}")
                        radar_data = read_bin_file(current_file_path)
                        results, detection_result = process_vital_signs(radar_data)

                        # 发送处理结果
                        self.processing_complete.emit({
                            'file_name': current_file,
                            'results': results,
                            'detection_result': detection_result
                        }, detection_result)

                        # 更新进度
                        self.file_processed.emit(self.current_file)
                        self.current_file += 1
                    except Exception as e:
                        self.status_message.emit(f"处理文件 {current_file} 出错: {str(e)}")
                else:
                    self.status_message.emit(f"文件不存在: {current_file}")
                    time.sleep(0.5)
            else:
                # 下一个文件不存在，等待
                time.sleep(0.5)

        self.status_message.emit("监测完成")
        self.monitoring_finished.emit()

    def stop(self):
        """停止线程"""
        self.is_running = False


# ========================
# 主界面类
# ========================
class HeartMonitorApp(QMainWindow):
    """心脏监测系统主界面"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("非接触式心脏监测系统 - RK3588毫米波雷达")
        self.setGeometry(100, 100, 1200, 700)

        # 设置应用图标
        self.setWindowIcon(QIcon("heart_icon.png"))

        # 初始化数据
        self.ecg_data = []
        self.pcg_data = []
        self.heart_rate = 0
        self.resp_rate = 0
        self.hrv = 0
        self.abnormalities = []
        self.monitoring_start_time = None
        self.monitoring_end_time = None
        self.patient_info = {
            "name": "张某某",
            "age": 58,
            "gender": "男",
            "id": "P20240001",
            "phone": "13800138000",
            "condition": "高血压病史",
            "doctor": "李医生"
        }
        self.is_monitoring = False
        self.start_time = datetime.datetime.now()
        self.current_time = 0
        self.data_processor = None
        self.folder_path = r"C:\Users\muqingchen\Desktop\毫米波\data\people"
        self.current_file = 1
        self.max_files = 100

        # 创建UI
        self.init_ui()

        # 状态更新定时器
        self.status_timer = QTimer(self)
        self.status_timer.timeout.connect(self.update_status)
        self.status_timer.start(1000)

        # 临时文件存储
        self.temp_files = []

    def init_ui(self):
        """初始化用户界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(10, 10, 10, 10)
        main_layout.setSpacing(10)

        # 顶部患者信息栏
        patient_info_layout = QHBoxLayout()
        patient_info_layout.setSpacing(20)

        self.patient_info_label = QLabel(f"患者: {self.patient_info['name']} | 年龄: {self.patient_info['age']} | "
                                         f"性别: {self.patient_info['gender']} | ID: {self.patient_info['id']} | "
                                         f"病史: {self.patient_info['condition']}")
        self.patient_info_label.setFont(QFont("Microsoft YaHei", 10, QFont.Bold))
        patient_info_layout.addWidget(self.patient_info_label)

        self.edit_patient_btn = QPushButton("编辑患者信息")
        self.edit_patient_btn.setFont(QFont("Microsoft YaHei", 9))
        self.edit_patient_btn.setStyleSheet("background-color: #6264a7; color: white;")
        self.edit_patient_btn.clicked.connect(self.edit_patient_info)
        patient_info_layout.addWidget(self.edit_patient_btn)

        main_layout.addLayout(patient_info_layout)

        # 分割布局：波形图+指标
        splitter = QSplitter(Qt.Vertical)

        # 波形图区域
        waveform_group = QGroupBox("实时波形监测")
        waveform_layout = QVBoxLayout(waveform_group)

        self.ecg_plot = RealTimePlotWidget("重构心电信号 (ECG)")
        self.pcg_plot = RealTimePlotWidget("重构心音信号 (PCG)")

        waveform_layout.addWidget(self.ecg_plot)
        waveform_layout.addWidget(self.pcg_plot)

        splitter.addWidget(waveform_group)

        # 指标区域
        indicator_group = QGroupBox("生命指标与诊断")
        indicator_layout = QVBoxLayout(indicator_group)
        indicator_layout.setSpacing(10)

        # 指标显示
        indicator_top_layout = QHBoxLayout()

        self.heart_rate_indicator = MedicalIndicator("心率", "bpm")
        self.resp_rate_indicator = MedicalIndicator("呼吸频率", "rpm")
        self.hrv_indicator = MedicalIndicator("心率变异性", "ms")

        indicator_top_layout.addWidget(self.heart_rate_indicator)
        indicator_top_layout.addWidget(self.resp_rate_indicator)
        indicator_top_layout.addWidget(self.hrv_indicator)

        indicator_layout.addLayout(indicator_top_layout)
        indicator_layout.addSpacing(10)

        # 异常检测和诊断建议区域
        diagnosis_layout = QVBoxLayout()
        diagnosis_layout.setSpacing(10)

        abnormality_layout = QVBoxLayout()
        self.abnormality_label = QLabel("<b>异常检测结果:</b>")
        self.abnormality_label.setFont(QFont("Microsoft YaHei", 10))
        abnormality_layout.addWidget(self.abnormality_label)

        self.abnormality_list = QLabel("等待监测数据...")
        self.abnormality_list.setFont(QFont("Microsoft YaHei", 9))
        self.abnormality_list.setStyleSheet("background-color: #f8f9fa; padding: 10px; border-radius: 5px;")
        self.abnormality_list.setMinimumHeight(80)
        abnormality_layout.addWidget(self.abnormality_list)

        diagnosis_suggestion_layout = QVBoxLayout()
        self.diagnosis_label = QLabel("<b>DeepSeek诊断建议:</b>")
        self.diagnosis_label.setFont(QFont("Microsoft YaHei", 10))
        diagnosis_suggestion_layout.addWidget(self.diagnosis_label)

        self.diagnosis_text = QLabel("等待监测数据...")
        self.diagnosis_text.setFont(QFont("Microsoft YaHei", 9))
        self.diagnosis_text.setStyleSheet("background-color: #f8f9fa; padding: 10px; border-radius: 5px;")
        self.diagnosis_text.setWordWrap(True)
        self.diagnosis_text.setMinimumHeight(120)
        diagnosis_suggestion_layout.addWidget(self.diagnosis_text)

        abnormality_layout.addSpacing(10)
        diagnosis_suggestion_layout.addSpacing(10)

        diagnosis_layout.addLayout(abnormality_layout)
        diagnosis_layout.addSpacing(5)
        diagnosis_layout.addLayout(diagnosis_suggestion_layout)

        indicator_layout.addLayout(diagnosis_layout)

        # 控制按钮
        button_layout = QHBoxLayout()

        self.start_btn = QPushButton("开始监测")
        self.start_btn.setFont(QFont("Microsoft YaHei", 10))
        self.start_btn.setStyleSheet("background-color: #0078d4; color: white; padding: 8px;")
        self.start_btn.clicked.connect(self.start_monitoring)

        self.stop_btn = QPushButton("停止监测")
        self.stop_btn.setFont(QFont("Microsoft YaHei", 10))
        self.stop_btn.setStyleSheet("background-color: #d83b01; color: white; padding: 8px;")
        self.stop_btn.clicked.connect(self.stop_monitoring)
        self.stop_btn.setEnabled(False)

        self.report_btn = QPushButton("生成报告")
        self.report_btn.setFont(QFont("Microsoft YaHei", 10))
        self.report_btn.setStyleSheet("background-color: #107c10; color: white; padding: 8px;")
        self.report_btn.clicked.connect(self.generate_report)
        self.report_btn.setEnabled(False)

        button_layout.addWidget(self.start_btn)
        button_layout.addWidget(self.stop_btn)
        button_layout.addWidget(self.report_btn)

        indicator_layout.addLayout(button_layout)

        splitter.addWidget(indicator_group)
        splitter.setSizes([400, 300])
        main_layout.addWidget(splitter)

        # 状态栏
        self.statusBar().showMessage("系统就绪 | RK3588毫米波雷达心脏监测系统 | 版本 1.0")

        # 创建菜单栏
        self.create_menu()

        # 更新初始状态
        self.update_indicators()

    def create_menu(self):
        """创建菜单栏"""
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu('文件')

        new_patient_action = QAction('新建患者', self)
        new_patient_action.triggered.connect(self.new_patient)
        file_menu.addAction(new_patient_action)

        save_report_action = QAction('保存报告', self)
        save_report_action.triggered.connect(self.save_report)
        file_menu.addAction(save_report_action)

        export_data_action = QAction('导出数据', self)
        export_data_action.triggered.connect(self.export_data)
        file_menu.addAction(export_data_action)

        exit_action = QAction('退出', self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 设置菜单
        settings_menu = menubar.addMenu('设置')

        device_settings_action = QAction('设备设置', self)
        device_settings_action.triggered.connect(self.device_settings)
        settings_menu.addAction(device_settings_action)

        ai_settings_action = QAction('AI模型设置', self)
        ai_settings_action.triggered.connect(self.ai_settings)
        settings_menu.addAction(ai_settings_action)

        # 帮助菜单
        help_menu = menubar.addMenu('帮助')

        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

    def update_indicators(self):
        """更新指标显示"""
        if not self.is_monitoring:
            self.heart_rate_indicator.set_value("--")
            self.resp_rate_indicator.set_value("--")
            self.hrv_indicator.set_value("--")
            self.heart_rate_indicator.set_warning(False)
            self.resp_rate_indicator.set_warning(False)
            self.hrv_indicator.set_warning(False)
            return

        self.heart_rate_indicator.set_value(self.heart_rate)
        self.resp_rate_indicator.set_value(self.resp_rate)
        self.hrv_indicator.set_value(self.hrv)

        if self.heart_rate < 55 or self.heart_rate > 100:
            self.heart_rate_indicator.set_warning(True)
        else:
            self.heart_rate_indicator.set_warning(False)

        if self.resp_rate < 12 or self.resp_rate > 20:
            self.resp_rate_indicator.set_warning(True)
        else:
            self.resp_rate_indicator.set_warning(False)

        if self.hrv < 30:
            self.hrv_indicator.set_warning(True)
        else:
            self.hrv_indicator.set_warning(False)

    def simulate_abnormalities(self):
        """模拟异常检测结果"""
        if random.random() < 0.1:
            abnormalities = []

            if self.heart_rate > 100:
                abnormalities.append("窦性心动过速")
            elif self.heart_rate < 55:
                abnormalities.append("窦性心动过缓")

            if self.hrv < 30:
                abnormalities.append("自主神经功能异常")

            if random.random() < 0.05:
                abnormalities.append("房性早搏")

            if random.random() < 0.03:
                abnormalities.append("ST段压低(心肌缺血可能)")

            if random.random() < 0.02:
                abnormalities.append("心音分裂")

            if random.random() < 0.01:
                abnormalities.append("收缩期杂音")

            self.abnormalities = abnormalities

        if self.abnormalities:
            abnormality_text = "<br>".join([f"• {abn}" for abn in self.abnormalities])
            self.abnormality_list.setText(abnormality_text)

            if "ST段压低" in self.abnormalities:
                self.diagnosis_text.setText(
                    "检测到ST段压低，提示心肌缺血可能。建议进行进一步心电图检查，评估冠状动脉供血情况。患者有高血压病史，需密切监测血压变化。")
            elif "心音分裂" in self.abnormalities:
                self.diagnosis_text.setText(
                    "检测到心音分裂，提示可能存在束支传导阻滞。建议进行超声心动图检查以评估心脏结构和功能。")
            elif "收缩期杂音" in self.abnormalities:
                self.diagnosis_text.setText(
                    "检测到收缩期杂音，提示可能存在瓣膜疾病。建议进行心脏超声检查评估二尖瓣和主动脉瓣功能。")
            else:
                self.diagnosis_text.setText(
                    "检测到轻度心律失常，建议24小时动态心电图监测。注意患者有无心悸、头晕等症状。定期随访观察。")
        else:
            self.abnormality_list.setText("未检测到显著异常")
            self.diagnosis_text.setText("心电、心音信号在正常范围内。自主神经功能正常。建议继续保持健康生活方式，定期复查。")

    def start_monitoring(self):
        """开始监测"""
        self.progress_dialog = QProgressDialog("毫米波雷达数据采集中...", "取消", 0, self.max_files, self)
        self.progress_dialog.setWindowTitle("正在初始化监测")
        self.progress_dialog.setWindowModality(Qt.WindowModal)
        self.progress_dialog.setAutoClose(True)
        self.progress_dialog.setAutoReset(True)
        self.progress_dialog.setMinimumDuration(0)
        self.progress_dialog.setStyleSheet("""
            QProgressDialog {
                background-color: white;
                border: 1px solid #0078d4;
                border-radius: 8px;
                padding: 15px;
            }
            QLabel {
                font-family: 'Microsoft YaHei';
                font-size: 12pt;
                color: #0078d4;
            }
            QProgressBar {
                border: 1px solid #0078d4;
                border-radius: 5px;
                text-align: center;
                background: white;
                height: 20px;
            }
            QProgressBar::chunk {
                background-color: #0078d4;
                width: 10px;
                border-radius: 3px;
            }
            QPushButton {
                background-color: #d83b01;
                color: white;
                border: none;
                padding: 5px 10px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #e81123;
            }
        """)
        cancel_button = self.progress_dialog.findChild(QPushButton)
        if cancel_button:
            cancel_button.setEnabled(False)
        self.progress_dialog.show()
        self.progress_dialog.setValue(0)

        self.statusBar().showMessage("毫米波雷达初始化中...")
        self.start_btn.setEnabled(False)
        self.abnormality_list.setText("雷达数据采集中...")
        self.diagnosis_text.setText("等待数据采集完成...")
        self.is_monitoring = True
        self.stop_btn.setEnabled(True)
        self.report_btn.setEnabled(True)
        self.monitoring_start_time = datetime.datetime.now()
        self.current_file = 1

        # 启动数据处理线程
        self.data_processor = DataProcessingThread(self.folder_path)
        self.data_processor.processing_complete.connect(self.update_ui_with_results)
        self.data_processor.file_processed.connect(self.update_progress)
        self.data_processor.status_message.connect(self.update_status_message)
        self.data_processor.monitoring_finished.connect(self.monitoring_completed)
        self.data_processor.start()

    def stop_monitoring(self):
        """停止监测"""
        if self.data_processor:
            self.data_processor.stop()
            self.data_processor.quit()
            self.data_processor.wait()
        self.is_monitoring = False
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.statusBar().showMessage("监测已停止")
        self.monitoring_end_time = datetime.datetime.now()

    def monitoring_completed(self):
        """监测完成"""
        self.is_monitoring = False
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.statusBar().showMessage("监测完成")
        self.monitoring_end_time = datetime.datetime.now()
        self.progress_dialog.close()
        QMessageBox.information(self, "监测完成", "所有文件处理完成，监测结束！")

    def update_status_message(self, message):
        """更新状态栏消息"""
        self.statusBar().showMessage(message)

    def update_progress(self, file_num):
        """更新进度条"""
        self.progress_dialog.setValue(file_num)
        self.progress_dialog.setLabelText(f"处理进度: {file_num}/{self.max_files} 文件")

    def update_ui_with_results(self, result_data, detection_result):
        """使用处理结果更新UI"""
        file_name = result_data['file_name']
        results = result_data['results']

        if not results:
            self.abnormality_list.setText(f"文件 {file_name}: 未检测到有效生命体征")
            self.diagnosis_text.setText(f"原因: {detection_result['reason']}")
            return

        # 只取第一个目标的结果
        result = results[0]

        # 更新指标
        self.heart_rate = result['heart_rate'] * 60  # 转换为BPM
        self.resp_rate = result['resp_rate'] * 60  # 转换为RPM
        self.hrv = result['heart_snr'] * 10  # 示例计算HRV

        # 更新UI显示
        self.update_indicators()

        # 更新心电和心音图
        if len(result['ecg_signal']) > 0:
            t_ecg = np.linspace(0, params.ecg_duration, len(result['ecg_signal']))
            self.ecg_plot.plot_static_data(result['ecg_signal'], t_ecg)

        if len(result['pcg_signal']) > 0:
            t_pcg = np.linspace(0, params.pcg_duration, len(result['pcg_signal']))
            self.pcg_plot.plot_static_data(result['pcg_signal'], t_pcg)

        # 更新异常检测结果
        self.simulate_abnormalities()

        # 更新状态栏
        self.statusBar().showMessage(
            f"文件 {file_name} 处理完成 | 心率: {self.heart_rate:.1f} BPM | 呼吸: {self.resp_rate:.1f} RPM")

    def update_status(self):
        """更新状态信息"""
        if self.is_monitoring:
            elapsed = datetime.datetime.now() - self.monitoring_start_time
            status = f"监测中 | 已处理 {self.current_file}/{self.max_files} 文件 | 持续时间: {elapsed.total_seconds():.1f}秒"
            self.statusBar().showMessage(status)

    def generate_report(self):
        """生成报告预览"""
        if not self.monitoring_end_time:
            QMessageBox.warning(self, "无法生成报告", "请先完成一次监测再生成报告")
            return

        # 生成报告HTML内容
        report_html = self.create_report_html()

        # 创建报告预览对话框
        preview_dialog = QDialog(self)
        preview_dialog.setWindowTitle("监测报告预览")
        preview_dialog.setGeometry(200, 200, 800, 600)

        layout = QVBoxLayout()

        # 添加文本编辑框显示报告
        text_edit = QTextEdit()
        text_edit.setReadOnly(True)
        text_edit.setHtml(report_html)
        layout.addWidget(text_edit)

        # 添加按钮
        button_layout = QHBoxLayout()

        save_btn = QPushButton("保存为PDF")
        save_btn.setStyleSheet("background-color: #107c10; color: white;")
        save_btn.clicked.connect(lambda: self.save_report())

        print_btn = QPushButton("打印报告")
        print_btn.setStyleSheet("background-color: #0078d4; color: white;")
        print_btn.clicked.connect(lambda: self.print_report(report_html))

        close_btn = QPushButton("关闭")
        close_btn.setStyleSheet("background-color: #d83b01; color: white;")
        close_btn.clicked.connect(preview_dialog.reject)

        button_layout.addWidget(save_btn)
        button_layout.addWidget(print_btn)
        button_layout.addWidget(close_btn)

        layout.addLayout(button_layout)
        preview_dialog.setLayout(layout)
        preview_dialog.exec_()

    def create_report_html(self):
        """创建HTML格式的报告"""
        duration = self.monitoring_end_time - self.monitoring_start_time
        duration_min = duration.total_seconds() / 60

        report = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <title>心脏监测报告</title>
            <meta charset="UTF-8">
            <style>
                body {{
                    font-family: 'Microsoft YaHei', sans-serif; 
                    margin: 0;
                    padding: 0;
                    background-color: #f8f9fa;
                    color: #333;
                }}
                .container {{
                    max-width: 900px;
                    margin: 0 auto;
                    padding: 20px;
                    background-color: white;
                    box-shadow: 0 0 20px rgba(0,0,0,0.1);
                }}
                .header {{
                    text-align: center;
                    margin-bottom: 30px;
                    border-bottom: 2px solid #0078d4;
                    padding-bottom: 20px;
                    position: relative;
                }}
                .hospital-logo {{
                    position: absolute;
                    left: 0;
                    top: 0;
                    width: 80px;
                    height: 80px;
                    background-color: #e6f2ff;
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-weight: bold;
                    color: #0078d4;
                }}
                .title {{
                    font-size: 28px;
                    font-weight: bold;
                    color: #0078d4;
                    margin-bottom: 10px;
                }}
                .subtitle {{
                    font-size: 18px;
                    color: #666666;
                    margin-bottom: 15px;
                }}
                .section {{
                    margin-bottom: 30px;
                    padding-bottom: 20px;
                    border-bottom: 1px solid #eaeaea;
                }}
                .section-title {{
                    font-size: 20px;
                    font-weight: bold;
                    color: #0078d4;
                    margin-bottom: 15px;
                    padding-bottom: 8px;
                    border-bottom: 2px solid #eaeaea;
                }}
                .info-table {{
                    width: 100%;
                    border-collapse: collapse;
                    margin-bottom: 20px;
                }}
                .info-table th, .info-table td {{
                    border: 1px solid #dddddd;
                    padding: 12px;
                    text-align: left;
                }}
                .info-table th {{
                    background-color: #f0f8ff;
                    font-weight: bold;
                    color: #0078d4;
                    width: 20%;
                }}
                .indicator-card {{
                    display: flex;
                    flex-direction: column;
                    margin: 10px;
                    padding: 15px;
                    border-radius: 8px;
                    background-color: #f8f9fa;
                    box-shadow: 0 2px 5px rgba(0,0,0,0.05);
                    text-align: center;
                    border-left: 4px solid #0078d4;
                }}
                .indicator-card.warning {{
                    border-left: 4px solid #e81123;
                    background-color: #fde7e9;
                }}
                .indicator-card .value {{
                    font-size: 28px;
                    font-weight: bold;
                    margin: 10px 0;
                    color: #0078d4;
                }}
                .indicator-card.warning .value {{
                    color: #e81123;
                }}
                .indicator-card .range {{
                    font-size: 12px;
                    color: #666;
                }}
                .indicator-container {{
                    display: flex;
                    justify-content: space-between;
                    margin-bottom: 20px;
                }}
                .abnormality-list {{
                    padding: 15px;
                    background-color: #f8f9fa;
                    border-radius: 8px;
                    border-left: 4px solid #6264a7;
                }}
                .signature {{
                    margin-top: 40px;
                    text-align: right;
                    padding-top: 20px;
                    border-top: 1px solid #eaeaea;
                }}
                .footer {{
                    margin-top: 30px;
                    text-align: center;
                    padding: 15px;
                    background-color: #f0f8ff;
                    border-radius: 8px;
                    font-size: 12px;
                    color: #666;
                }}
                .risk-level {{
                    display: inline-block;
                    padding: 3px 10px;
                    border-radius: 12px;
                    font-size: 12px;
                    font-weight: bold;
                    margin-left: 10px;
                }}
                .risk-low {{
                    background-color: #dff6dd;
                    color: #107c10;
                }}
                .risk-medium {{
                    background-color: #fff4ce;
                    color: #8a6d07;
                }}
                .risk-high {{
                    background-color: #fde7e9;
                    color: #e81123;
                }}
            </style>
        </head>
        <body>
            <div class="container">
                <div class="header">
                    <div class="hospital-logo">心<br>血<br>管</div>
                    <div class="title">非接触式心脏监测报告</div>
                    <div class="subtitle">毫米波雷达技术 | DeepSeek智能分析</div>
                    <div style="font-size: 14px; color: #888;">报告编号: HR-2024-{random.randint(10000, 99999)}</div>
                </div>

                <div class="section">
                    <div class="section-title">患者信息</div>
                    <table class="info-table">
                        <tr>
                            <th>姓名</th>
                            <td>{self.patient_info['name']}</td>
                            <th>年龄</th>
                            <td>{self.patient_info['age']}岁</td>
                        </tr>
                        <tr>
                            <th>性别</th>
                            <td>{self.patient_info['gender']}</td>
                            <th>患者ID</th>
                            <td>{self.patient_info['id']}</td>
                        </tr>
                        <tr>
                            <th>联系电话</th>
                            <td>{self.patient_info['phone']}</td>
                            <th>负责医生</th>
                            <td>{self.patient_info['doctor']}</td>
                        </tr>
                        <tr>
                            <th>病史</th>
                            <td colspan="3">{self.patient_info['condition']}</td>
                        </tr>
                    </table>
                </div>

                <div class="section">
                    <div class="section-title">监测信息</div>
                    <table class="info-table">
                        <tr>
                            <th>开始时间</th>
                            <td>{self.monitoring_start_time.strftime('%Y-%m-%d %H:%M:%S')}</td>
                            <th>结束时间</th>
                            <td>{self.monitoring_end_time.strftime('%Y-%m-%d %H:%M:%S')}</td>
                        </tr>
                        <tr>
                            <th>监测时长</th>
                            <td>{duration_min:.1f}分钟</td>
                            <th>设备编号</th>
                            <td>RK3588-MWR-001</td>
                        </tr>
                        <tr>
                            <th>处理文件数</th>
                            <td colspan="3">{self.current_file}/{self.max_files}</td>
                        </tr>
                    </table>
                </div>

                <div class="section">
                    <div class="section-title">关键生命指标</div>
                    <div class="indicator-container">
                        <div class="indicator-card {'warning' if self.heart_rate < 55 or self.heart_rate > 100 else ''}">
                            <div style="font-weight: bold; font-size: 16px;">心率</div>
                            <div class="value">{self.heart_rate:.1f} bpm</div>
                            <div class="range">正常范围: 55-100 bpm</div>
                        </div>

                        <div class="indicator-card {'warning' if self.resp_rate < 12 or self.resp_rate > 20 else ''}">
                            <div style="font-weight: bold; font-size: 16px;">呼吸频率</div>
                            <div class="value">{self.resp_rate:.1f} rpm</div>
                            <div class="range">正常范围: 12-20 rpm</div>
                        </div>

                        <div class="indicator-card {'warning' if self.hrv < 30 else ''}">
                            <div style="font-weight: bold; font-size: 16px;">心率变异性</div>
                            <div class="value">{self.hrv:.1f} ms</div>
                            <div class="range">正常范围: >30 ms</div>
                        </div>
                    </div>
                </div>

                <div class="section">
                    <div class="section-title">异常检测结果</div>
                    <div class="abnormality-list">
                        {''.join([f'<p>• {abn}</p>' for abn in self.abnormalities]) if self.abnormalities else '<p>• 未检测到显著异常</p>'}
                    </div>
                </div>

                <div class="section">
                    <div class="section-title">DeepSeek诊断建议</div>
                    <div class="abnormality-list">
                        <p>{self.diagnosis_text.text()}</p>
                    </div>
                </div>

                <div class="section">
                    <div class="section-title">临床建议</div>
                    <ul>
                        <li>建议定期监测心率和血压</li>
                        <li>保持健康饮食和适量运动</li>
                        <li>{"建议进一步心脏专科检查" if self.abnormalities else "继续当前治疗方案"}</li>
                        <li>下一次复查时间: {(datetime.datetime.now() + datetime.timedelta(days=30)).strftime('%Y-%m-%d')}</li>
                    </ul>
                </div>

                <div class="signature">
                    <p>报告生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
                    <p>_________________________</p>
                    <p>{self.patient_info['doctor']} 医生 | 心血管智能监测实验室</p>
                </div>

                <div class="footer">
                    <p>本报告由非接触式心脏监测系统生成 | 报告结果仅供参考，临床诊断请结合其他检查结果</p>
                    <p>© 2024 心血管智能监测实验室 | 保密级别: 内部使用</p>
                </div>
            </div>
        </body>
        </html>
        """

        return report

    def save_report(self):
        """保存报告为PDF"""
        if not self.monitoring_end_time:
            QMessageBox.warning(self, "无法生成报告", "请先完成一次监测再保存报告")
            return

        default_name = f"心脏监测报告_{self.patient_info['name']}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
        file_path, _ = QFileDialog.getSaveFileName(self, "保存报告", default_name, "PDF 文件 (*.pdf);;所有文件 (*)")

        if file_path:
            if not file_path.lower().endswith('.pdf'):
                file_path += '.pdf'

            doc = SimpleDocTemplate(file_path, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30,
                                    bottomMargin=30)
            styles = getSampleStyleSheet()

            title_style = ParagraphStyle('Title', parent=styles['Heading1'], fontSize=22, leading=26,
                                         textColor=colors.HexColor('#0078d4'), alignment=1, fontName=CHINESE_FONT,
                                         spaceAfter=12)
            subtitle_style = ParagraphStyle('Subtitle', parent=styles['Heading2'], fontSize=16,
                                            textColor=colors.HexColor('#666666'), alignment=1, fontName=CHINESE_FONT,
                                            spaceAfter=24)
            heading_style = ParagraphStyle('Heading', parent=styles['Heading2'], fontSize=16,
                                           textColor=colors.HexColor('#0078d4'), spaceAfter=12, fontName=CHINESE_FONT,
                                           underline=True, underlineColor=colors.HexColor('#0078d4'), underlineWidth=1)
            normal_style = ParagraphStyle('Normal', parent=styles['BodyText'], fontSize=11, spaceAfter=6,
                                          fontName=CHINESE_FONT, leading=16)
            signature_style = ParagraphStyle('Signature', parent=styles['BodyText'], fontSize=10,
                                             textColor=colors.grey, alignment=2, fontName=CHINESE_FONT, spaceBefore=30)
            footer_style = ParagraphStyle('Footer', parent=styles['BodyText'], fontSize=9,
                                          textColor=colors.HexColor('#666666'), alignment=1, fontName=CHINESE_FONT,
                                          spaceBefore=20)
            indicator_style = ParagraphStyle('Indicator', parent=styles['BodyText'], fontSize=14,
                                             textColor=colors.HexColor('#0078d4'), alignment=1, fontName=CHINESE_FONT,
                                             spaceAfter=6)

            elements = []
            elements.append(Paragraph("非接触式心脏监测报告", title_style))
            elements.append(Paragraph("毫米波雷达技术 | DeepSeek智能分析", subtitle_style))
            elements.append(Paragraph(f"报告编号: HR-2024-{random.randint(10000, 99999)}",
                                      ParagraphStyle(name='ReportNum', fontSize=10, textColor=colors.grey,
                                                     alignment=1, fontName=CHINESE_FONT, spaceAfter=24)))
            elements.append(Spacer(1, 12))
            elements.append(Table([[""]], colWidths=[doc.width],
                                  style=[('LINEABOVE', (0, 0), (-1, -1), 1, colors.HexColor('#0078d4'))]))
            elements.append(Spacer(1, 24))

            # 患者信息表格
            patient_info = [
                ["姓名:", self.patient_info['name']],
                ["年龄:", f"{self.patient_info['age']}岁"],
                ["性别:", self.patient_info['gender']],
                ["患者ID:", self.patient_info['id']],
                ["联系电话:", self.patient_info['phone']],
                ["病史:", self.patient_info['condition']],
                ["负责医生:", self.patient_info['doctor']]
            ]

            patient_table = Table(patient_info, colWidths=[2 * cm, 12 * cm])
            patient_table.setStyle(TableStyle([
                ('FONT', (0, 0), (-1, -1), CHINESE_FONT, 10),
                ('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#eaeaea')),
                ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f0f8ff')),
                ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#0078d4')),
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                ('ALIGN', (0, 0), (0, -1), 'RIGHT'),
                ('PADDING', (0, 0), (-1, -1), 8),
            ]))
            elements.append(patient_table)
            elements.append(Spacer(1, 24))

            # 监测信息表格
            duration = self.monitoring_end_time - self.monitoring_start_time
            duration_min = duration.total_seconds() / 60

            monitor_info = [
                ["开始时间:", self.monitoring_start_time.strftime('%Y-%m-%d %H:%M:%S')],
                ["结束时间:", self.monitoring_end_time.strftime('%Y-%m-%d %H:%M:%S')],
                ["监测时长:", f"{duration_min:.1f}分钟"],
                ["设备编号:", "RK3588-MWR-001"],
                ["处理文件数:", f"{self.current_file}/{self.max_files}"]
            ]

            monitor_table = Table(monitor_info, colWidths=[2 * cm, 12 * cm])
            monitor_table.setStyle(TableStyle([
                ('FONT', (0, 0), (-1, -1), CHINESE_FONT, 10),
                ('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#eaeaea')),
                ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f0f8ff')),
                ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#0078d4')),
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                ('ALIGN', (0, 0), (0, -1), 'RIGHT'),
                ('PADDING', (0, 0), (-1, -1), 8),
            ]))
            elements.append(monitor_table)
            elements.append(Spacer(1, 24))
            elements.append(PageBreak())

            # 关键指标卡片
            elements.append(Paragraph("关键生命指标", heading_style))
            elements.append(Spacer(1, 12))

            indicator_data = [
                [  # 心率卡片
                    Paragraph("心率", indicator_style),
                    Paragraph(f"<font size='20'><b>{self.heart_rate:.1f}</b></font> bpm",
                              ParagraphStyle(name='Value', alignment=1, fontName=CHINESE_FONT)),
                    Paragraph("正常范围: 55-100 bpm",
                              ParagraphStyle(name='Range', fontSize=9, textColor=colors.grey, alignment=1,
                                             fontName=CHINESE_FONT)),
                    Paragraph("风险: " + (
                        "<font color='#e81123'>高</font>" if self.heart_rate < 55 or self.heart_rate > 100 else "<font color='#107c10'>低</font>"),
                              ParagraphStyle(name='Risk', fontSize=9, alignment=1, fontName=CHINESE_FONT))
                ],
                [  # 呼吸频率卡片
                    Paragraph("呼吸频率", indicator_style),
                    Paragraph(f"<font size='20'><b>{self.resp_rate:.1f}</b></font> rpm",
                              ParagraphStyle(name='Value', alignment=1, fontName=CHINESE_FONT)),
                    Paragraph("正常范围: 12-20 rpm",
                              ParagraphStyle(name='Range', fontSize=9, textColor=colors.grey, alignment=1,
                                             fontName=CHINESE_FONT)),
                    Paragraph("风险: " + (
                        "<font color='#e81123'>高</font>" if self.resp_rate < 12 or self.resp_rate > 20 else "<font color='#107c10'>低</font>"),
                              ParagraphStyle(name='Risk', fontSize=9, alignment=1, fontName=CHINESE_FONT))
                ],
                [  # HRV卡片
                    Paragraph("心率变异性", indicator_style),
                    Paragraph(f"<font size='20'><b>{self.hrv:.1f}</b></font> ms",
                              ParagraphStyle(name='Value', alignment=1, fontName=CHINESE_FONT)),
                    Paragraph("正常范围: >30 ms",
                              ParagraphStyle(name='Range', fontSize=9, textColor=colors.grey, alignment=1,
                                             fontName=CHINESE_FONT)),
                    Paragraph("风险: " + (
                        "<font color='#e81123'>高</font>" if self.hrv < 30 else "<font color='#107c10'>低</font>"),
                              ParagraphStyle(name='Risk', fontSize=9, alignment=1, fontName=CHINESE_FONT))
                ]
            ]

            indicator_table = Table(indicator_data, colWidths=[6 * cm, 6 * cm, 6 * cm])
            indicator_table.setStyle(TableStyle([
                ('FONT', (0, 0), (-1, -1), CHINESE_FONT, 10),
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f8f9fa')),
                ('BACKGROUND', (1, 0), (1, -1), colors.HexColor('#f8f9fa')),
                ('BACKGROUND', (2, 0), (2, -1), colors.HexColor('#f8f9fa')),
                ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#eaeaea')),
                ('PADDING', (0, 0), (-1, -1), 12),
                ('BORDER', (0, 0), (-1, -1), 1, colors.HexColor('#eaeaea')),
                ('LEFTPADDING', (0, 0), (-1, -1), 8),
                ('RIGHTPADDING', (0, 0), (-1, -1), 8),
                ('BOX', (0, 0), (0, -1), 1, colors.HexColor('#0078d4')),
                ('BOX', (1, 0), (1, -1), 1, colors.HexColor('#0078d4')),
                ('BOX', (2, 0), (2, -1), 1, colors.HexColor('#0078d4')),
            ]))
            elements.append(indicator_table)
            elements.append(Spacer(1, 24))

            # 异常检测结果
            elements.append(Paragraph("异常检测结果", heading_style))
            elements.append(Spacer(1, 8))

            if self.abnormalities:
                abnormality_text = "<br/>".join([f"• {abn}" for abn in self.abnormalities])
                elements.append(Paragraph(abnormality_text, normal_style))
            else:
                elements.append(Paragraph("• 未检测到显著异常", normal_style))

            elements.append(Spacer(1, 16))

            # 诊断建议
            elements.append(Paragraph("DeepSeek诊断建议", heading_style))
            elements.append(Spacer(1, 8))
            elements.append(Paragraph(self.diagnosis_text.text(), normal_style))
            elements.append(Spacer(1, 24))

            # 临床建议
            elements.append(Paragraph("临床建议", heading_style))
            elements.append(Spacer(1, 8))
            advice = [
                "• 建议定期监测心率和血压",
                "• 保持健康饮食和适量运动",
                f"• {'建议进一步心脏专科检查' if self.abnormalities else '继续当前治疗方案'}",
                f"• 下一次复查时间: {(datetime.datetime.now() + datetime.timedelta(days=30)).strftime('%Y-%m-%d')}"
            ]
            for item in advice:
                elements.append(Paragraph(item, normal_style))

            elements.append(Spacer(1, 24))

            # 添加波形图截图
            try:
                ecg_img_data = self.ecg_plot.capture_plot()
                pcg_img_data = self.pcg_plot.capture_plot()

                ecg_temp = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
                if ecg_img_data:
                    ecg_temp.write(ecg_img_data)
                ecg_temp.close()

                pcg_temp = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
                if pcg_img_data:
                    pcg_temp.write(pcg_img_data)
                pcg_temp.close()

                self.temp_files.extend([ecg_temp.name, pcg_temp.name])

                elements.append(Paragraph("监测波形图", heading_style))
                elements.append(Spacer(1, 12))

                wave_table_data = [
                    [Image(ecg_temp.name, width=7 * cm, height=4 * cm) if ecg_img_data else "",
                     Image(pcg_temp.name, width=7 * cm, height=4 * cm) if pcg_img_data else ""]
                ]

                wave_table = Table(wave_table_data, colWidths=[7 * cm, 7 * cm])
                wave_table.setStyle(TableStyle([
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                    ('LEFTPADDING', (0, 0), (-1, -1), 0),
                    ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                    ('TOPPADDING', (0, 0), (-1, -1), 0),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 0),
                ]))
                elements.append(wave_table)
                elements.append(Spacer(1, 12))

                caption_table = Table([
                    ["重构心电信号 (ECG)", "重构心音信号 (PCG)"]
                ], colWidths=[7 * cm, 7 * cm])
                caption_table.setStyle(TableStyle([
                    ('FONT', (0, 0), (-1, -1), CHINESE_FONT, 9),
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('TEXTCOLOR', (0, 0), (-1, -1), colors.HexColor('#666666')),
                    ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#f9f9f9')),
                    ('LEFTPADDING', (0, 0), (-1, -1), 0),
                    ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                    ('TOPPADDING', (0, 0), (-1, -1), 4),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
                ]))
                elements.append(caption_table)
                elements.append(Spacer(1, 24))
            except Exception as e:
                print(f"无法添加波形图: {str(e)}")

            # 签名
            elements.append(
                Paragraph(f"报告生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", signature_style))
            elements.append(Paragraph("_________________________", signature_style))
            elements.append(Paragraph(f"{self.patient_info['doctor']} 医生 | 心血管智能监测实验室", signature_style))

            # 页脚
            footer_text = "本报告由非接触式心脏监测系统生成 | 报告结果仅供参考，临床诊断请结合其他检查结果"
            elements.append(Spacer(1, 24))
            elements.append(Paragraph(footer_text, footer_style))
            elements.append(Paragraph("© 2024 心血管智能监测实验室 | 保密级别: 内部使用", footer_style))

            # 生成PDF
            doc.build(elements)

            # 清理临时文件
            for temp_file in self.temp_files:
                try:
                    os.unlink(temp_file)
                except:
                    pass
            self.temp_files = []

            QMessageBox.information(self, "保存成功", f"报告已成功保存至:\n{file_path}")

    def print_report(self, report_html):
        """打印报告"""
        QMessageBox.information(self, "打印报告", "打印功能将在完整版中提供")

    def new_patient(self):
        """新建患者"""
        dialog = PatientInfoDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            self.patient_info = dialog.get_patient_info()
            self.patient_info_label.setText(
                f"患者: {self.patient_info['name']} | 年龄: {self.patient_info['age']} | "
                f"性别: {self.patient_info['gender']} | ID: {self.patient_info['id']} | "
                f"病史: {self.patient_info['condition']}"
            )
            self.statusBar().showMessage(f"已创建新患者: {self.patient_info['name']}")

    def edit_patient_info(self):
        """编辑患者信息"""
        dialog = PatientInfoDialog(self)
        dialog.name_edit.setText(self.patient_info['name'])
        dialog.age_spin.setValue(self.patient_info['age'])
        dialog.gender_combo.setCurrentText(self.patient_info['gender'])
        dialog.id_edit.setText(self.patient_info['id'])
        dialog.phone_edit.setText(self.patient_info['phone'])
        dialog.condition_edit.setText(self.patient_info['condition'])
        dialog.doctor_edit.setText(self.patient_info['doctor'])

        if dialog.exec_() == QDialog.Accepted:
            self.patient_info = dialog.get_patient_info()
            self.patient_info_label.setText(
                f"患者: {self.patient_info['name']} | 年龄: {self.patient_info['age']} | "
                f"性别: {self.patient_info['gender']} | ID: {self.patient_info['id']} | "
                f"病史: {self.patient_info['condition']}"
            )
            self.statusBar().showMessage(f"患者信息已更新: {self.patient_info['name']}")

    def export_data(self):
        """导出数据"""
        file_path, _ = QFileDialog.getSaveFileName(self, "导出数据", "", "CSV 文件 (*.csv);;所有文件 (*)")
        if file_path:
            self.statusBar().showMessage(f"数据已导出至: {file_path}")

    def device_settings(self):
        """设备设置"""
        self.statusBar().showMessage("打开设备设置...")
        QMessageBox.information(self, "设备设置", "毫米波雷达参数配置功能将在完整版中提供")

    def ai_settings(self):
        """AI模型设置"""
        self.statusBar().showMessage("配置AI模型...")
        QMessageBox.information(self, "AI设置", "DeepSeek模型参数配置功能将在完整版中提供")

    def show_about(self):
        """显示关于信息"""
        about_text = """
        <h2>非接触式心脏监测系统</h2>
        <p><b>版本:</b> 1.0</p>
        <p><b>技术说明:</b></p>
        <p>本系统基于毫米波雷达技术，通过非接触方式捕捉人体胸壁振动信号，结合深度学习算法重构临床级心电(ECG)和心音(PCG)波形，实时监测心脏活动状态。</p>

        <p><b>雷达技术:</b></p>
        <p>采用FMCW（Frequency Modulated Continuous Wave，调频连续波）雷达技术，通过连续发射频率线性变化的微波信号，接收反射信号并与发射信号混频，提取微弱的生命体征信号。</p>

        <p><b>系统参数:</b></p>
        <ul>
            <li>工作频率: 60 GHz</li>
            <li>带宽: 4 GHz</li>
            <li>chirp持续时间: 100 ms</li>
            <li>距离分辨率: 约3.75 cm</li>
            <li>速度分辨率: 约0.05 m/s</li>
        </ul>

        <p><b>核心功能:</b></p>
        <ul>
            <li>非接触式心脏监测</li>
            <li>实时心电、心音波形重构</li>
            <li>心率、呼吸频率、HRV等关键指标分析</li>
            <li>心律失常、ST段异常、心音异常等病理特征识别</li>
            <li>结构化电子病历生成</li>
        </ul>

        <p><b>硬件平台:</b> RK3588嵌入式系统</p>
        <p><b>AI引擎:</b> DeepSeek多模态大模型</p>
        <p><b>© 2024 心血管智能监测实验室</b></p>
        """

        QMessageBox.about(self, "关于", about_text)

    def closeEvent(self, event):
        """清理临时文件"""
        for temp_file in self.temp_files:
            try:
                os.unlink(temp_file)
            except:
                pass
        event.accept()


# ========================
# 应用程序入口
# ========================
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")

    palette = QPalette()
    palette.setColor(QPalette.Window, QColor(240, 240, 240))
    palette.setColor(QPalette.WindowText, QColor(0, 0, 0))
    palette.setColor(QPalette.Base, QColor(255, 255, 255))
    palette.setColor(QPalette.AlternateBase, QColor(233, 231, 253))
    palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 220))
    palette.setColor(QPalette.ToolTipText, QColor(0, 0, 0))
    palette.setColor(QPalette.Text, QColor(0, 0, 0))
    palette.setColor(QPalette.Button, QColor(240, 240, 240))
    palette.setColor(QPalette.ButtonText, QColor(0, 0, 0))
    palette.setColor(QPalette.BrightText, QColor(255, 0, 0))
    palette.setColor(QPalette.Highlight, QColor(0, 120, 215))
    palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255))
    app.setPalette(palette)

    app.setFont(QFont("Microsoft YaHei", 9))

    window = HeartMonitorApp()
    window.show()
    sys.exit(app.exec_())